using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
using JobShopModel.Events;
using JobShopModel.Model.Transport;
using JobShopModel.Model.Station;
using JobShopModel.Model.JobLvl;
using JobShopModel.Model.FactoryLvl;

namespace JobShopModel.Model.FactoryLvl
{
    /// <summary>
    /// Factory, main class of the simulation.
    /// Implement Singleton Design Patern, can only exist one instance of it in the program.
    /// TODO: Documentation of main class
    /// </summary>
    public class Factory
    {
        //Instance of factory
        private static Factory myFactory;

        //Curent Time
        private Clock clock;
        private EventQueue eventQueue;
        private Dictionary<StationsName,IStations> listStations;
        private Transporter transporter;
        private FactoryMap map;
        private JobStats stats;

        /// <summary>
        /// Static Method, Singleton implementation
        /// return instance of the factory if it exits, if not create it.
        /// </summary>
        /// <returns>Instance of Factory</returns>
        public static Factory getInstance()
        {
            if (myFactory == null)
            {
                myFactory = new Factory();
            }
            return myFactory;
        }

        /// <summary>
        /// Default COnstructor
        /// </summary>
        private Factory()
        {
            //Initialize the elements of the class
            initFactory();
        }


        /// <summary>
        /// Initialisation method that set up all the element of the factory
        /// </summary>
        public void initFactory()
        {
            //Create all the elements
            this.clock = new Clock();
            //this.eventQueue = new EventQueue(); -- COMMENTED OUT TO TEST HEAP IMPLEMENTATION
            this.eventQueue = new EventQueue(new List<IEvent>());

            this.transporter = new Transporter();
            
            //Set up the Job Statistic
            this.stats = new JobStats();

            //Set up the map of the stations
            this.map = new FactoryMap();
           
        }

        /// <summary>
        /// Initialize the Jobs.
        /// It will add to the EventQueue CreateJob events at random time
        /// distributed during the whole time of the simulation.
        /// </summary>
        /// <param name="timeLimit">Limit when we end adding jobs</param>
        public void initJobList(Time timeLimit)
        {
            List<IEvent> tmpList = new List<IEvent>();
            Time tmp = new Time();
            Time tmp2;
            Random rand = new Random();
            Extern.Exponential exp = new Extern.Exponential();
            while (tmp < timeLimit)
            {
                tmp2 = new Time(0, exp.calculate(.25));
                tmp = tmp + tmp2;

                if (tmp < timeLimit)
                    tmpList.Add(new CreateJob(tmp));
                //create a new job whose start time is the working time (tmp) plus an exponential variable with mean
                //.25 hours
            }

            //Add an empty job at the end of the simulation
            tmpList.Add(new EmptyEvent(timeLimit));

            this.eventQueue = new EventQueue(tmpList);
            
        }

        public void initStationsList(int[] numMachines)
        {
            //Set up all the Stations
            this.listStations = new Dictionary<StationsName, IStations>();
            this.listStations.Add(StationsName.IOSTATION, new StationIO());
            this.listStations.Add(StationsName.STATION1, new RegularStation(StationsName.STATION1, numMachines[0], clock.CurrentTime));
            this.listStations.Add(StationsName.STATION2, new RegularStation(StationsName.STATION2, numMachines[1], clock.CurrentTime));
            this.listStations.Add(StationsName.STATION3, new RegularStation(StationsName.STATION3, numMachines[2], clock.CurrentTime));
            this.listStations.Add(StationsName.STATION4, new RegularStation(StationsName.STATION4, numMachines[3], clock.CurrentTime));
            this.listStations.Add(StationsName.STATION5, new RegularStation(StationsName.STATION5, numMachines[4], clock.CurrentTime));

        }
       

    #region Properties

        /// <summary>
        /// Current time of the simulation
        /// Automaticaly increment the time
        /// </summary>
        public Time CurrentTime
        {
            get
            {
                return this.clock.CurrentTime;
            }
            set
            {
                this.clock.CurrentTime = value;
            }

        }

        /// <summary>
        /// List of the stations inside the factory
        /// ReadOnly,must be setup at the initialisation
        /// </summary>
        public Dictionary<StationsName,IStations> Stations
        {
            get
            {
                return listStations;
            }
        }

        /// <summary>
        /// Transporter of the Facotry
        /// ReadOnly,must be setup at the initialisation
        /// </summary>
        public Transporter Transporter
        {
            get { return this.transporter; }
        }

        /// <summary>
        /// Gets an instance of the statistics being tracked for this factory
        /// </summary>
        public JobStats JobStatistics
        {
            get
            {
                return this.stats;
            }
        }

        /// <summary>
        /// EventQueue, Keep track of all the event
        /// ReadOnly,must be setup at the initialisation
        /// </summary>
        public EventQueue EventQueue
        {
            get { return this.eventQueue; }
        }

        /// <summary>
        /// Return the distance between 2 stations.
        /// </summary>
        /// <param name="start">First Station</param>
        /// <param name="end">Second Station</param>
        /// <returns></returns>
        public int DistanceToStation(StationsName start, StationsName end)
        {
            return map.getDistance(start, end);
        }

        #endregion Properties

    }


   
}
